require "Common/EnumConst"
require "Battle/Entity/Entity"
require "Battle/FightEntity"
require "Battle/Entity/PetEntity"
require "Battle/Manager/SpineEffectManager"
require "Battle/BattleScene"

---
local enabled = nil;
local devossEnabled = nil;
local endpoint = nil;
local accessKeyId = "<yourAccessKeyId>";
local accessKeySecret = "<yourAccessKeySecret>";
local bucketName = nil;

local fightsPath = nil;

local ossClient = nil;

local battleTest = nil;
---@class FightController
FightController = ClientFight.CreateClass("FightController")
ClientFight.FightController = FightController;
local this = FightController;
this.batchBattleInfo = {};--多段战斗的
this.batchBattleResult = {};--多段战斗的结果
this.batchBattleIdx = 1;--多段战斗的阶段索引
this.curBatchBattleInfo = {};--当前多段战斗的batchBattleInfo
this.addBatchBattleInfo = {};--加赛的
this.monsterWinTeam = {};
this.battleReports = {};
function FightController.init()
    this.batchBattleInfo = {};--多段战斗的
    this.batchBattleResult = {};--多段战斗的结果
    this.batchBattleIdx = 1;--多段战斗的阶段索引
    this.curBatchBattleInfo = {};--当前多段战斗的batchBattleInfo
    this.addBatchBattleInfo = {};--加赛的
    this.monsterWinTeam = {};
    this.battleReports = {};
    --this.batchBattleInfo = {};--多段战斗的
    --this.batchBattleResult = {};--多段战斗的结果
    --this.batchBattleIdx = 1;--多段战斗的阶段索引
    --this.curBatchBattleInfo = {};--当前多段战斗的batchBattleInfo
    --if (enabled or devossEnabled) then
    --    local config = ClientBuilderConfiguration.New();
    --    config.RequestTimeoutEnabled = true;
    --    config.RequestTimeout = 3000;
    --    --ossClient = OSSClientBuilder.New().build(endpoint, accessKeyId, accessKeySecret, config);
    --end
end
function FightController.battleTest(battleInfo)
    if (battleTest) then
        local battleResult = ClientFight.BattleResult.New();
        battleResult.code = ErrorCode.PLATFORM_SUCCESS.id;
        battleResult.msg = "成功";
        return battleResult;
    end
    battleTest = true;
    local start = os.time();
    local fightId = 0;

    -- 战斗逻辑
    local fight = Fight.New();
    fight.fightId = battleInfo.fightId;
    fight.fightType = battleInfo.fightType;
    fight.reportSaveTypeEnum = battleInfo.reportSaveTypeEnum;

    local b = saveCmds(battleInfo.serverId, fight, Collections.emptyList());
    if (not b) then
        local battleResult = ClientFight.BattleResult.New();
        battleResult.code = ErrorCode.FIGHT_SERVER_ERROR.id;
        battleResult.msg = "";
        log.info("战报保存失败 ={} ", JSON.toJSONString(battleInfo));
        return battleResult;
    end

    local battleResult = ClientFight.BattleResult.New();
    battleResult.code = ErrorCode.PLATFORM_SUCCESS.id;
    battleResult.msg = "成功";
    return battleResult;

    --log.error(e.getMessage(), e);
    --log.info("战斗报错阵容={} ", JSON.toJSONString(battleInfo));
    --BattleResult battleResult = new BattleResult();
    --battleResult.setCode(ErrorCode.FIGHT_SERVER_ERROR.getId());
    --battleResult.setMsg(e.getMessage());
    --return battleResult;
    ----log.info("战斗计算时间: {}ms , fightId = {} , playerId = {}", os.time() - start, fightId, battleInfo.getPlayerId());
end

function FightController.battle(battleInfo)

    local start = os.time();
    local fightId = 0;

    -- 战斗逻辑
    local fight = FightClientManager.init(battleInfo);
    FightClientManager.start(fight);
    fightId = fight.fightId;
    local fightInfo = buildFightInfo(battleInfo.serverId, battleInfo.version, battleInfo.playerId, battleInfo.atkTeam, battleInfo.defTeam, fight);
    local extraInfo = battleInfo.extraInfo;
    if (extraInfo ~= nil or table.getn(extraInfo) > 0) then
        for key, entry in pairs(extraInfo) do
            fightInfo.extraInfo[key] = entry;
        end
    end

    local fightResult = calcWin(battleInfo.winType, battleInfo, fight, fightInfo);

    --local b = saveCmds(battleInfo.serverId(), fight, fightInfo.cmds());
    --if (not b) then
    --    --                BattleResult battleResult = new BattleResult();
    --    --                battleResult.setCode(ErrorCode.FIGHT_SERVER_ERROR.getId());
    --    --                battleResult.setMsg("");
    --    --                return battleResult;
    --end

    local battleResult = buildBattleResult(fight, fightInfo, fightResult, battleInfo.serverId());
    if (not b) then
        battleResult.noFight = 2;
    end
    return battleResult;
end
--log.error(e.getMessage(), e);
--log.info("战斗报错阵容={} ", JSON.toJSONString(battleInfo));
--BattleResult battleResult = new BattleResult();
--battleResult.setCode(ErrorCode.FIGHT_SERVER_ERROR.getId());
--battleResult.setMsg(e.getMessage());
--return battleResult;
--} finally {
--log.info("战斗计算时间: {}ms , fightId = {} , playerId = {}", os.time() - start, fightId, battleInfo.getPlayerId());
--}
--}

function FightController.doBatchBattleResult()
    local nextBattle = true;--是否有下次战斗
    local battleStage = this.batchBattleIdx;
    this.battleReports[tostring(battleStage)] = getJsonStr(this.batchBattleInfo[battleStage].fight.actions);
    if battleStage <= table.getn(this.batchBattleInfo) then

        local fight = this.batchBattleInfo[battleStage].fight;
        local battleInfo = this.batchBattleInfo[battleStage].battleInfo;
        local batchBattleInfo = this.curBatchBattleInfo;
        local firstBattleInfo = this.batchBattleInfo[1].battleInfo;
        local serverId = firstBattleInfo.serverId;
        local version = firstBattleInfo.version;
        local playerId = firstBattleInfo.playerId;
        local playerWinTeam = nil;
        local fightInfo = this.buildFightInfo(serverId, version, playerId, battleInfo.atkTeam, battleInfo.defTeam, fight);

        local extraInfo = battleInfo.extraInfo;
        if (extraInfo ~= nil and table.getn(extraInfo) > 0) then
            for key, entry in pairs(extraInfo) do
                fightInfo.extraInfo[key] = entry;
            end
        end

        local fightResult = calcWin(battleInfo.winType, battleInfo, fight, fightInfo);

        local result = this.buildBattleResult(fight, fightInfo, fightResult, serverId);
        --if (not b) then
        --    result.noFight = 2;
        --end
        this.batchBattleResult[tostring(battleStage)]  = result

        if (fightResult == 1) then
            playerWinTeam = this.buildTeamByFightTeam(battleInfo.atkTeam,1, fight);
        else
            local defTeamTemp = battleInfo.defTeam;
            if (battleInfo.fightType == FightTypeConst.SOLO_HAOS_TEST) then
                defTeamTemp = this.batchBattleInfo[1].battleInfo.defTeam;
            end
            this.monsterWinTeam = this.buildTeamByFightTeam(defTeamTemp, 2, fight);
        end
        type = battleInfo.fightType;
        if (battleInfo.fightType == FightTypeConst.SOLO_HAOS_TEST and fightResult == 1) then
            --玩家直接把boss打死了,直接算赢
            nextBattle = false;
        end
        if battleStage == table.getn(this.curBatchBattleInfo.battleInfos) then
            nextBattle = false;
        end

        --end
        if battleStage == table.getn(this.curBatchBattleInfo.battleInfos) then

            -- 最后一回合胜负未分
            local flag = true;
            if (type == FightTypeConst.ARENA or type == FightTypeConst.GUILD_BATTLE) then
                flag = false;
            end
            if (type == FightTypeConst.SOLO_HAOS_TEST) then
                flag = false;
            end

            if (flag) then
                this.addBatchBattleInfo = {}
                local result1 = this.batchBattleResult[1];
                local result2 = this.batchBattleResult[2];
                if (result1.win ~= result2.win) then

                    -- 加时赛战斗信息
                    battleInfo = ClientFight.BattleInfo.New();
                    battleInfo.serverId = serverId;
                    battleInfo.fightId = overtimeFightId;
                    battleInfo.atkTeam = playerWinTeam;
                    battleInfo.defTeam = this.monsterWinTeam;
                    battleInfo.randSeed = batchBattleInfo.randSeed;
                    battleInfo.damageSuppress = batchBattleInfo.damageSuppress;
                    battleInfo.hitrateSuppress = batchBattleInfo.hitrateSuppress;
                    battleInfo.maxBattleSeconds = batchBattleInfo.maxBattleSeconds;
                    if (batchBattleInfo.reportSaveTypeEnum ~= ReportSaveTypeEnum.TYPE_DEFAULT.id) then
                        battleInfo.reportSaveTypeEnum = batchBattleInfo.reportSaveTypeEnum;
                    end

                    -- 初始化战斗
                    fight = FightClientManager.init(battleInfo, false);
                    FightClientManager.start(fight);
                    this.addBatchBattleInfo.battleInfo = battleInfo;
                    this.addBatchBattleInfo.fight = fight;
                    this.batchBattleIdx = this.batchBattleIdx + 1;
                    BattlePlaybackManager.playClientBattle(battleInfo.fightType)
                    return nil;
                end
            end

        end
    else
        local addInfo = this.addBatchBattleInfo.battleInfo;
        local addFight = this.addBatchBattleInfo.fight;
        --触发加赛
        local fightInfo = buildFightInfo(addInfo.serverId, addInfo.version, addInfo.playerId, addInfo.playerWinTeam, addInfo.monsterWinTeam, addFight);

        --local b = saveCmds(battleInfo.serverId, fight, fightInfo.cmds);
        --if (not b) then
        --    --                        BatchBattleResult errorResult = new BatchBattleResult();
        --    --                        errorResult.setCode(ErrorCode.FIGHT_SERVER_ERROR.getId());
        --    --                        errorResult.setMsg("");
        --    --                        return errorResult;
        --end

        local fightResult = calcWin(addInfo.winType, addInfo, addFight, fightInfo);

        local result = this:buildBattleResult(addFight, fightInfo, fightResult, addInfo.serverId);
        if (not b) then
            result.noFight = 2;
        end
        -- 加时赛信息附带进去
        result.battleInfo = addInfo;

        this.batchBattleResult[tostring(battleStage+1)]  = result;
    end
    if nextBattle then
        this.batchBattleIdx = this.batchBattleIdx + 1;
        return this.curBatchBattleInfo, this.batchBattleIdx;
    end
end

function FightController.batchBattle(batchBattleInfo, battleStage)
    local start = os.time();
    local fightList = {};
    local playerWinTeam = nil;
    local battleInfos = batchBattleInfo.battleInfos;

    if not battleStage then
        battleStage = 1;
    end
    if battleStage == 1 then
        this.init();
        for i, v in ipairs(battleInfos) do

            this.batchBattleInfo[i] = {};
            this.batchBattleInfo[i].battleInfo = v;
        end
        this.batchBattleResult = {};
    end


    local monsterWinTeam = nil;
    this.curBatchBattleInfo = batchBattleInfo;
    local overtimeFightId = 0;
    local o = batchBattleInfo["overtimeFightId"];
    if (o ~= nil) then
        overtimeFightId = o;
    end


    --for i = 1, table.getn(battleInfos) do
    local battleInfo = battleInfos[battleStage];
    if (battleStage > 1 and battleInfo.fightType == FightTypeConst.SOLO_HAOS_TEST) then
        battleInfo.defTeam = this.monsterWinTeam;
    end
    battleInfo.damageSuppress = batchBattleInfo.damageSuppress;
    battleInfo.hitrateSuppress = batchBattleInfo.hitrateSuppress;
    if (batchBattleInfo.reportSaveTypeEnum ~= ReportSaveTypeEnum.enum.TYPE_DEFAULT.id) then
        battleInfo.reportSaveTypeEnum = batchBattleInfo.reportSaveTypeEnum;
    end
    -- 初始化战斗

    local fight = FightClientManager.init(battleInfo, false);
    FightClientManager.start(fight);


    this.batchBattleInfo[battleStage].fight = fight;
    BattlePlaybackManager.playClientBattle(battleInfo.fightType)

    return batchBattleResult;
end

function FightController.needOvertimeBattle(type)
    if (type == FightTypeConst.ARENA or type == FightTypeConst.GUILD_BATTLE) then
        return false;
    end
    if (type == FightTypeConst.SOLO_HAOS_TEST) then
        return false;
    end
    return true;
end
--[[
* 保存战斗命令
*
* @param fight 战斗id
* @param cmds    战斗命令
* @throws Exception
*/]]
function FightController.saveCmds(serverId, fight, cmds)
    local savePath = fight.reportSavePath(serverId);
    local json = JSON.toJSONString(cmds);
    this:saveTxtLogToDevOss(fight, savePath);
    --if (enabled or devossEnabled) then
    --    return OssUtil.OssUpload(ossClient, bucketName, "fight/" + savePath .. ".bt", json);
    --else
    --    local fileName = fightsPath .. "/" + savePath .. ".bt";
    --    local dirName = string.sub(fileName,1,string.find(fileName,"/",1));
    --    local file = File.New(dirName);
    --    if (not file.exists()) then
    --        file.mkdirs();
    --    end
    --    FileUtils.writeFileText(fileName, json);
    --    return true;
    --end
end

function FightController.saveTxtLogToDevOss(fight, savePath)
    --if (not devossEnabled) then
    --    return ;
    --end
    --local name = "fightlogs/" + String.valueOf(fight.fightId) .. "_txt.log";
    --local text = FileUtils.readFileText(name);
    --if (StringUtils.isEmpty(text)) then
    --    return ;
    --end
    --OssUtil.OssUpload(ossClient, bucketName, "fight/" + savePath .. "_txt.log", text);
end
--[[
* 打加时赛，用剩余队友重建参战队伍
*
* @param team      参战队伍
* @param fightTeam 战斗队伍
* @return 参战队伍
*/]]
function FightController.buildTeamByFightTeam(team, index, fight)
    -- TODO: 需要保留战斗buff，目前只设置了血量和怒气
    local fightTeam = fight.teams[index];
    for i, member in pairs(team.members) do
        local fightersList = {};
        for j, v in ipairs(fightTeam.fighters) do
            if v.partnerId ~= member.partnerId then
                table.insert(fightersList, v)
            end
        end
        local isContinue = false;
        local o = fightersList[1];
        if (o ~= nil) then
            local fighter = o;
            if (fighter.hp <= 0) then
                isContinue = true;
            end
            if not isContinue then
                member.initHp = fighter.hp;
                if (fight.fightType ~= FightTypeConst.SOLO_HAOS_TEST) then
                    member.initEnergy = fighter.energy;
                else
                    local map = member.attributeMap[CalculatorTypeEnum.BASE];
                    if (map ~= nil) then
                        map[FighterAttributeEnum.HP.id] = fighter.hp;
                    end
                end

            end
        end
    end
    return team;
end


--[[
* 根据战斗胜利结算类型，计算胜利结果
*
* @param winType    战斗胜利结算类型
* @param battleInfo 战斗请求信息
* @param fight      战斗实例
* @param fightInfo  战斗信息
* @return 胜利结果
*/]]
function FightController.calcWin(winType, battleInfo, fight, fightInfo)
    if winType == WinTypeEnum.TOTAL_HP then

        -- 好友战胜利结果条件
        -- 1. 战斗队伍成员剩余血量总和高得获得胜利
        -- 2. 血量相等时，战力高得获得胜利
        -- 3. 战力相同时，防守方胜利
        local attackerLeftHp = 0;
        local defenderLeftHp = 0;
        local fightTeams = fight.teams;

        if (fightTeams == nil or table.getn(fightTeams) < 2) then
            log.error("calcWin error. the count of the fight teams is less than 2. fightId: " .. fight.fightId);
            return 0;
        end

        for i, fighter in pairs(fightTeams[1].fighters) do

            if (not fighter:isSummons()) then
                attackerLeftHp = attackerLeftHp + fighter.hp;
            end
        end
        for i, fighter in pairs(fightTeams[1].fighters) do
            if (not fighter:isSummons()) then
                defenderLeftHp = defenderLeftHp + fighter.hp;
            end
        end

        if (attackerLeftHp > defenderLeftHp) then
            return 1;
        elseif (attackerLeftHp < defenderLeftHp) then
            return 0;
        else
            return battleInfo.atkTeam.power > battleInfo.defTeam.power and 1 or 0;
        end
    else
        if ((fight.finish == 2 and 1 or 0) > 0) then
            return 1;
        else
            return 0;
        end
    end
end

function FightController.buildBattleResult(fight, fightInfo, fightResult, ServerId)
    local battleResult = ClientFight.BattleResult.New();
    battleResult.fightId = fight.fightId;
    battleResult.time = fightInfo.time;
    battleResult.win = fightResult;
    battleResult.oss = enabled and 1 or 0;
    battleResult.devOss = devossEnabled and 1 or 0;
    battleResult.extraInfo = fightInfo.extraInfo;
    --battleResult.fightReportPath = fight:reportSavePath(ServerId);
    battleResult.version = Global.FIGHT_REPORT_VERSION;

    for i, fightTeam in pairs(fight.teams) do
        for i1, fighter in pairs(fightTeam.fighters) do
            if (not fighter.summons) then
                battleResult.currHps[fighter.partnerId] = fighter.hp;
                battleResult.currEnergys[fighter.partnerId] = fighter.energy;
                battleResult.maxHp[fighter.partnerId] = FighterAttributeManager.getFightAttr(fighter, FighterAttributeEnum.HP);
            end
        end
        for i2, fighter in pairs(fightTeam.alternates) do
            battleResult.currHps[fighter.getPartnerId] = fighter.hp;
            battleResult.currEnergys[fighter.getPartnerId] = fighter.energy;
            battleResult.maxHp[fighter.getPartnerId] = FighterAttributeManager.getFightAttr(fighter, FighterAttributeEnum.HP);
        end
    end
    -- 战斗数据
    for i, fightMember in pairs(fightInfo.myMembers) do
        local memberStat = fightInfo.myStatistics[tostring(fightMember.partnerId)];
        if (memberStat == nil) then
            memberStat = FighterStats.New();
        end
        table.insert(battleResult.atkMemberStats, memberStat);
    end

    for i, fightMember in pairs(fightInfo.foeMembers) do
        local memberStat = fightInfo.foeStatistics[tostring(fightMember.partnerId)];
        if (memberStat == nil) then
            memberStat = FighterStats.New();
        end
        table.insert(battleResult.defMemberStats, memberStat);
    end
    return battleResult;
end

--[[
* 战斗信息
*
* @param serverId
* @param version
* @param playerId
* @param team0
* @param team1
* @param fight
* @return
*/]]
function FightController.buildFightInfo(serverId, version, playerId, team0, team1, fight)

    local info = ClientFight.FightInfo.New();
    info.version = version;
    info.serverId = serverId;
    info.fightId = fight.fightId;
    info.playerId = playerId;
    info.time = fight.frame * Global.FIGHT_FRAME / 1000;
    info.battleTime = os.time();
    for i, fighter in pairs(fight.teams[1].fighters) do
        if (not fighter.summons) then
            info.myStatistics[tostring(fighter.partnerId)] = fighter.fighterStats;
        end
    end

    -- 保存阵容
    local tmList = {};
    for i, member in pairs(team0.members) do

        local tm = FightMember.New();
        tm.partnerI = member.partnerId;
        tm.partnerModelI = member.partnerModelId;
        tm.leve = member.level;
        tm.sta = member.star;
        tm.weaponLeve = member.weaponLevel;
        table.insert(tmList, tm);
    end

    info.myMembers = tmList;

    tmList = {};

    for i, fighter in pairs(fight.teams[1].fighters) do

        if (not fighter.summons) then
            info.foeStatistics[tostring(fighter.partnerId)] = fighter.fighterStats;
        end
    end

    for i, member in pairs(team1.members) do

        local tm = FightMember.New();
        tm.partnerId = member.partnerId;
        tm.partnerModelId = member.partnerModelId;
        tm.level = member.level;
        tm.star = member.star;
        tm.weaponLevel = member.weaponLevel;
        table.insert(tmList, tm);
    end

    info.doeMembers = tmList;

    info.cmds = fight.actions;
    return info;
end